Skip to content

feat: move artist copy flow server-side#398

Open
arpitgupta1214 wants to merge 3 commits intotestfrom
codex/server-copy-client-redirect-api
Open

feat: move artist copy flow server-side#398
arpitgupta1214 wants to merge 3 commits intotestfrom
codex/server-copy-client-redirect-api

Conversation

@arpitgupta1214
Copy link
Copy Markdown
Collaborator

@arpitgupta1214 arpitgupta1214 commented Apr 6, 2026

Summary

  • move artist chat copying into the API flow
  • add a generic redirect tool to the create artist chain
  • reuse shared copy logic for both the endpoint and chat completion sync

Testing

  • pnpm test lib/chats/tests/copyChatMessages.test.ts lib/chats/tests/copyChatMessagesHandler.test.ts lib/mcp/tools/artists/tests/registerCreateNewArtistTool.test.ts lib/chat/toolChains/tests/toolChains.test.ts

Summary by cubic

Moves the artist handoff fully to the server and finalizes it after chat completion. When create_new_artist runs, the server creates a new room, copies the chat, and streams a redirect to the new room before sending the final finish.

  • New Features

    • Added handleCreateArtistRedirect to parse create_new_artist (tool or dynamic-tool), run copyRoom + copyChatMessages, and return a redirect path.
    • handleChatStream now accepts an optional finish callback; it sets sendFinish: false, emits data-redirect, then writes the final finish. The chat API wires this up.
  • Refactors

    • Extracted copyChatMessages and updated the copy messages API handler to use it.
    • Removed room creation and newRoomId from registerCreateNewArtistTool; it now returns only artistAccountId.

Written for commit e3d8ab6. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Enhanced artist creation with automatic chat room setup and intelligent redirection.
    • Improved chat message management when creating new artists.
  • Bug Fixes

    • Refined chat streaming completion handling to better support post-response workflows.

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
recoup-api Ready Ready Preview Apr 6, 2026 7:47pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 6, 2026

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0acb0b5d-510e-4806-a10b-ba5a9b6866b6

📥 Commits

Reviewing files that changed from the base of the PR and between cf9f181 and e3d8ab6.

⛔ Files ignored due to path filters (4)
  • lib/chat/__tests__/handleChatCompletion.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/chat/__tests__/handleChatStream.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/chat/__tests__/handleCreateArtistRedirect.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/chats/__tests__/copyChatMessagesHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (5)
  • app/api/chat/route.ts
  • lib/chat/handleChatStream.ts
  • lib/chat/handleCreateArtistRedirect.ts
  • lib/chats/copyChatMessages.ts
  • lib/chats/copyChatMessagesHandler.ts
 ______________________________________________________________________
< I am here to kick bugs and chew carrots. And I'm all out of carrots. >
 ----------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
📝 Walkthrough

Walkthrough

The changes implement post-chat-completion processing for artist creation workflows. When a "create_new_artist" tool completes successfully, the system now automatically copies the conversation room and its messages to the newly created artist's account, then redirects the user to the new space.

Changes

Cohort / File(s) Summary
Chat Completion Handler
lib/chat/handleChatCompletion.ts
Added logic to extract artist creation results from tool outputs, initiate room copying with copyRoom(), duplicate chat messages with copyChatMessages(), and return a redirect path for the client to navigate to the new room.
Tool Registration Cleanup
lib/mcp/tools/artists/registerCreateNewArtistTool.ts
Removed newRoomId property from CreateNewArtistResult and eliminated room-copying logic that was previously delegated to the tool itself.
Stream Handler Pipeline
lib/chat/handleChatStream.ts
Refactored streaming pipeline to wrap toUIMessageStream() with a custom onFinish callback that handles post-completion processing (artist result extraction, room copying, redirect emission) instead of relying on top-level finish handlers.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Stream as Chat Stream
    participant Handler as Chat Completion<br/>Handler
    participant Tool as Tool Output
    participant RoomSvc as Room Service
    participant DB as Database

    Client->>Stream: Send chat message
    Stream->>Tool: Process tool execution
    Tool->>DB: Create new artist account
    Tool-->>Stream: Return tool result
    
    Stream->>Handler: onFinish: Call handleChatCompletion()
    Handler->>Tool: Extract create_new_artist result
    Tool-->>Handler: Return artistAccountId
    
    Handler->>RoomSvc: copyRoom(roomId, artistAccountId)
    RoomSvc->>DB: Create new room copy
    DB-->>RoomSvc: Return newRoomId
    RoomSvc-->>Handler: Return newRoomId
    
    Handler->>DB: copyChatMessages(sourceChatId,<br/>targetChatId: newRoomId)
    DB-->>Handler: Messages duplicated
    
    Handler-->>Stream: Return { redirectPath }
    Stream->>Stream: Emit data-redirect message
    Stream-->>Client: Send redirect response
    Client->>Client: Navigate to /chat/newRoomId
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🎨 An artist emerges from the chat's creative flow,
Their room is kindly copied, with messages all in tow,
The stream redirects smoothly to their brand-new space,
Where conversations flourish at a brand-new place! ✨

🚥 Pre-merge checks | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning Pull request violates SOLID principles: missing module import, broken error handling that erases redirect paths, SRP violations in handleChatCompletion, and incomplete data-redirect UI consumer. Create missing copyChatMessages.ts, isolate handleSendEmailToolOutputs error handling, extract getCreateArtistResult, and implement or remove data-redirect consumer handler.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/server-copy-client-redirect-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/chat/handleChatCompletion.ts (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Duplicate UIMessage import.

UIMessage is imported on line 1 and again on line 16. Remove the duplicate.

🔧 Proposed fix
-import type { UIMessage } from "ai";
 import selectAccountEmails from "@/lib/supabase/account_emails/selectAccountEmails";
 ...
 import { copyChatMessages } from "@/lib/chats/copyChatMessages";
 import type { CreateNewArtistResult } from "@/lib/mcp/tools/artists/registerCreateNewArtistTool";
-import { getToolOrDynamicToolName, isToolOrDynamicToolUIPart, type UIMessage } from "ai";
+import { getToolOrDynamicToolName, isToolOrDynamicToolUIPart, type UIMessage } from "ai";

Keep only the line 16 import that also brings in the helper functions.

Also applies to: 16-16

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/chat/handleChatCompletion.ts` at line 1, There is a duplicate import of
UIMessage; remove the standalone import declaration that only imports UIMessage
and keep the combined import statement that brings in UIMessage plus the helper
functions (the import that also exports the helpers referenced elsewhere in this
module), ensuring no other symbols are removed and imports remain used.
🧹 Nitpick comments (1)
lib/mcp/tools/registerRedirectTool.ts (1)

1-32: Consider moving to a domain subfolder for consistency.

Per coding guidelines, MCP tool files should be located in lib/mcp/tools/[domain]/register[ToolName]Tool.ts. This file is at the root of lib/mcp/tools/. Consider moving it to a domain folder like lib/mcp/tools/navigation/registerRedirectTool.ts or lib/mcp/tools/ui/registerRedirectTool.ts.

That said, the implementation itself is clean—Zod schema for input validation, proper use of getToolResultSuccess/getToolResultError, and sensible path validation requiring a leading /.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mcp/tools/registerRedirectTool.ts` around lines 1 - 32, Move this tool
module into a domain-named subfolder under the tools directory (for example a
"navigation" or "ui" domain) to match project conventions; keep the exported
function registerRedirectTool and the redirectSchema unchanged, update any
import/exports or references that point to this file so they use the new module
location, and verify tests/builds pick up the new path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/chat/handleChatCompletion.ts`:
- Around line 116-129: The block calling getCreateArtistRoomIds and
copyChatMessages duplicates the synchronous copy in
registerCreateNewArtistTool.ts; decide whether this post-completion copy is a
fallback or the single source of truth and act accordingly: either remove the
duplicate call here and rely on registerCreateNewArtistTool's copy, or keep it
but change the copyChatMessages call to use clearExisting: false (and add a
brief comment above the createArtistRoomIds loop referencing
registerCreateNewArtistTool.ts to explain the intentional redundancy as a
fallback); update references to roomId and responseMessages as needed when
consolidating.

In `@lib/mcp/tools/artists/registerCreateNewArtistTool.ts`:
- Around line 111-119: The MCP tool currently calls copyChatMessages (in
registerCreateNewArtistTool.ts) with clearExisting: true, duplicating work and
risking a race with the copy performed later in handleChatCompletion; remove the
copyChatMessages call and its error branch from the tool so the tool only
returns the newRoomId (and any metadata) and lets handleChatCompletion perform
the single canonical copy using responseMessages. Ensure
registerCreateNewArtistTool.ts no longer references copyResult or
getToolResultError for that copy path and that the returned tool result still
includes newRoomId so handleChatCompletion can detect and perform the copy.

---

Outside diff comments:
In `@lib/chat/handleChatCompletion.ts`:
- Line 1: There is a duplicate import of UIMessage; remove the standalone import
declaration that only imports UIMessage and keep the combined import statement
that brings in UIMessage plus the helper functions (the import that also exports
the helpers referenced elsewhere in this module), ensuring no other symbols are
removed and imports remain used.

---

Nitpick comments:
In `@lib/mcp/tools/registerRedirectTool.ts`:
- Around line 1-32: Move this tool module into a domain-named subfolder under
the tools directory (for example a "navigation" or "ui" domain) to match project
conventions; keep the exported function registerRedirectTool and the
redirectSchema unchanged, update any import/exports or references that point to
this file so they use the new module location, and verify tests/builds pick up
the new path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bdebd082-f559-4aa9-ad86-0165a6ff8d76

📥 Commits

Reviewing files that changed from the base of the PR and between e5d1265 and 7e203f8.

⛔ Files ignored due to path filters (4)
  • lib/chat/toolChains/__tests__/toolChains.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/chats/__tests__/copyChatMessages.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/chats/__tests__/copyChatMessagesHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/mcp/tools/artists/__tests__/registerCreateNewArtistTool.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (7)
  • lib/chat/handleChatCompletion.ts
  • lib/chat/toolChains/createNewArtistToolChain.ts
  • lib/chats/copyChatMessages.ts
  • lib/chats/copyChatMessagesHandler.ts
  • lib/mcp/tools/artists/registerCreateNewArtistTool.ts
  • lib/mcp/tools/index.ts
  • lib/mcp/tools/registerRedirectTool.ts

Comment on lines +111 to +119
const copyResult = await copyChatMessages({
sourceChatId: active_conversation_id,
targetChatId: newRoomId,
clearExisting: true,
});

if (!copyResult.success) {
return getToolResultError(copyResult.error ?? "Failed to copy artist conversation");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Duplicate copyChatMessages call creates a race condition.

This MCP tool calls copyChatMessages with clearExisting: true here, but handleChatCompletion.ts (lines 116-129) also calls it for the same newRoomId after parsing the tool output. Both use clearExisting: true, which deletes target memories before inserting.

The sequence:

  1. MCP tool executes → copies messages → returns newRoomId in result
  2. handleChatCompletion parses newRoomId from tool output → copies messages again

At best, this is redundant work. At worst, if timing varies, the second caller could clear messages the first just inserted, then re-copy potentially stale data.

Recommendation: Keep the copy logic in one location only. Since handleChatCompletion already handles post-completion tasks and has full context of responseMessages, consider removing the copyChatMessages call from this MCP tool and relying solely on handleChatCompletion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mcp/tools/artists/registerCreateNewArtistTool.ts` around lines 111 - 119,
The MCP tool currently calls copyChatMessages (in
registerCreateNewArtistTool.ts) with clearExisting: true, duplicating work and
risking a race with the copy performed later in handleChatCompletion; remove the
copyChatMessages call and its error branch from the tool so the tool only
returns the newRoomId (and any metadata) and lets handleChatCompletion perform
the single canonical copy using responseMessages. Ensure
registerCreateNewArtistTool.ts no longer references copyResult or
getToolResultError for that copy path and that the returned tool result still
includes newRoomId so handleChatCompletion can detect and perform the copy.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 11 files

Confidence score: 2/5

  • High risk: access control is missing in lib/mcp/tools/artists/registerCreateNewArtistTool.ts, allowing copying messages from rooms the caller may not own
  • Open redirect risk in lib/mcp/tools/registerRedirectTool.ts if protocol-relative paths are accepted, which could allow leaving the app
  • Score is lower because these are concrete, user-impacting security issues (with moderate/high severity) beyond minor style concerns
  • Pay close attention to lib/mcp/tools/artists/registerCreateNewArtistTool.ts, lib/mcp/tools/registerRedirectTool.ts, lib/chats/copyChatMessages.ts, lib/chat/handleChatCompletion.ts - access control, redirect validation, error handling, and duplicate import cleanup.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/chat/handleChatCompletion.ts">

<violation number="1" location="lib/chat/handleChatCompletion.ts:16">
P1: Custom agent: **Enforce Clear Code Style and Maintainability Practices**

Rule 2 (strict TypeScript maintainability) is violated by re-importing `UIMessage` on the new `ai` import line, creating a duplicate import that breaks compilation. Remove the duplicate type specifier from this added import.</violation>
</file>

<file name="lib/mcp/tools/registerRedirectTool.ts">

<violation number="1" location="lib/mcp/tools/registerRedirectTool.ts:25">
P2: Reject protocol-relative paths ("//...") to avoid allowing redirects outside the app when the tool expects a relative in-app path.</violation>
</file>

<file name="lib/mcp/tools/artists/registerCreateNewArtistTool.ts">

<violation number="1" location="lib/mcp/tools/artists/registerCreateNewArtistTool.ts:112">
P1: Validate that active_conversation_id belongs to the resolved account (or the caller has access) before copying messages; otherwise an API caller can copy messages from rooms they don’t own by supplying a different room ID.</violation>
</file>

<file name="lib/chats/copyChatMessages.ts">

<violation number="1" location="lib/chats/copyChatMessages.ts:49">
P2: Handle insertMemories errors so copyChatMessages consistently returns a CopyChatMessagesResult instead of throwing on insert failures.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant UI as Client / UI
    participant API as Chat Completion Service
    participant Tool as Artist MCP Tool
    participant Helper as NEW: copyChatMessages (Helper)
    participant DB as Supabase / Database

    Note over UI,DB: Artist Creation & Server-Side Copy Flow

    UI->>API: POST /api/chat
    API->>Tool: execute "create_new_artist"
    
    Tool->>DB: createArtistInDb()
    Tool->>DB: copyRoom()
    
    Tool->>Helper: NEW: copyChatMessages(source, target)
    Helper->>DB: selectMemories(sourceRoom)
    DB-->>Helper: source messages
    
    opt clearExisting is true
        Helper->>DB: CHANGED: deleteMemories(targetRoom)
    end
    
    Helper->>DB: CHANGED: insertMemories(targetRoom, messages)
    Helper-->>Tool: success: boolean
    
    Tool-->>API: returns { newRoomId }
    
    Note over API,Tool: Tool Chain Progression
    
    API->>Tool: NEW: execute "redirect" tool (/chat/<newRoomId>)
    Tool-->>API: redirect action
    
    Note over API,Helper: Post-Completion Sync
    
    API->>API: getCreateArtistRoomIds(responseMessages)
    loop for each newRoomId
        API->>Helper: NEW: copyChatMessages (ensure sync)
        Helper-->>API: result
    end
    
    API-->>UI: response + redirect instruction
    UI->>UI: Client-side redirect to new artist room

    Note over UI,Helper: Standalone API Endpoint Flow (Refactored)
    
    UI->>API: POST /api/chats/[id]/messages/copy
    API->>Helper: CHANGED: use centralized copyChatMessages()
    Helper-->>API: result
    API-->>UI: 200 OK (success)
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

inputSchema: redirectSchema,
},
async ({ path }: RedirectArgs) => {
if (!path.startsWith("/")) {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Reject protocol-relative paths ("//...") to avoid allowing redirects outside the app when the tool expects a relative in-app path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/mcp/tools/registerRedirectTool.ts, line 25:

<comment>Reject protocol-relative paths ("//...") to avoid allowing redirects outside the app when the tool expects a relative in-app path.</comment>

<file context>
@@ -0,0 +1,32 @@
+      inputSchema: redirectSchema,
+    },
+    async ({ path }: RedirectArgs) => {
+      if (!path.startsWith("/")) {
+        return getToolResultError("Redirect path must start with '/'.");
+      }
</file context>
Fix with Cubic

}
}

const copiedCount = await insertMemories(
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Handle insertMemories errors so copyChatMessages consistently returns a CopyChatMessagesResult instead of throwing on insert failures.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/chats/copyChatMessages.ts, line 49:

<comment>Handle insertMemories errors so copyChatMessages consistently returns a CopyChatMessagesResult instead of throwing on insert failures.</comment>

<file context>
@@ -0,0 +1,63 @@
+    }
+  }
+
+  const copiedCount = await insertMemories(
+    sourceMemories.map(memory => ({
+      id: generateUUID(),
</file context>
Fix with Cubic

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
lib/chat/handleChatCompletion.ts (2)

26-49: Split getCreateArtistResult into scan + parse helpers.

This helper now mixes message traversal with dynamic-tool deserialization, which makes malformed-output behavior harder to test. As per coding guidelines, **/*.{js,ts,tsx,jsx,py,java,cs,go,rb,php}: "Flag functions longer than 20 lines" and "Keep functions small and focused."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/chat/handleChatCompletion.ts` around lines 26 - 49, The
getCreateArtistResult function mixes traversal and parsing—split it into two
focused helpers: one scanner (e.g., scanForCreateArtistPart(responseMessages:
UIMessage[]): UIPart | null) that iterates responseMessages and message.parts to
locate the matching part where getToolOrDynamicToolName(part) ===
"create_new_artist" and part.state === "output-available", and a parser (e.g.,
parseCreateArtistResult(part: UIPart): CreateNewArtistResult | null) that
handles the two cases: if part.type === "dynamic-tool" read
part.output?.content?.[0]?.text and try JSON.parse (return null on parse
errors), otherwise return part.output as CreateNewArtistResult; then update
getCreateArtistResult to call the scanner and parser and preserve existing
behavior (skip malformed outputs and return null if none found).

120-145: Move the artist-room copy/redirect flow into its own helper.

This branch adds room cloning, message copying, and redirect selection to an already busy completion handler; extracting it would make the success/failure paths much easier to reason about. As per coding guidelines, lib/**/*.ts: "Apply Single Responsibility Principle (SRP): one exported function per file; each file should do one thing well" and "Keep functions under 50 lines", and **/*.{js,ts,tsx,jsx,py,java,cs,go,rb,php}: "Keep functions small and focused."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/chat/handleChatCompletion.ts` around lines 120 - 145, The artist-room
cloning/copy/redirect block should be extracted into a small helper to keep
handleChatCompletion focused: create a new async function named
processArtistRoomCopyAndGetRedirect(roomId: string, responseMessages:
MessageType[]) that encapsulates the getCreateArtistResult -> copyRoom ->
copyChatMessages logic and returns the redirectPath string | undefined,
preserving the same console.error paths on failures; replace the inlined block
in handleChatCompletion with a single await call to this helper and return its
result (ensuring handleSendEmailToolOutputs(responseMessages) is still awaited
afterward); keep the new helper under ~50 lines and only export it if other
modules need it.
lib/chat/handleChatStream.ts (1)

44-74: Extract the onFinish workflow into a named helper.

This callback now mixes abort handling, response-message selection, completion side effects, redirect emission, finish emission, and credit deduction. As per coding guidelines, **/*.{js,ts,tsx,jsx,py,java,cs,go,rb,php}: "Flag functions longer than 20 lines", "Keep functions small and focused", and "Avoid deep nesting (max 3 levels)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/chat/handleChatStream.ts` around lines 44 - 74, The onFinish callback is
too large and should be extracted into a focused named helper: create an async
function (e.g., handleOnFinishEvent or finalizeChatStream) that accepts the
event plus required locals (writer, body, streamResult, DEFAULT_MODEL) and move
the entire callback body into it—preserve the early return when event.isAborted,
the assistantMessages selection logic, the call to handleChatCompletion (with
body and responseMessages), emitting the redirect via writer.write if
redirectPath exists, emitting the finish event with finishReason, and calling
handleChatCredits using await streamResult!.usage and body.model ??
DEFAULT_MODEL; then replace the inline onFinish implementation with onFinish:
async event => await handleOnFinishEvent(event, writer, body, streamResult).
Ensure you keep the same async/await behavior and imports/closures so types and
behavior remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/chat/handleChatCompletion.ts`:
- Around line 142-153: The current flow calls
handleSendEmailToolOutputs(responseMessages) before returning redirectPath, so
any exception there causes the outer catch to return {} and drop the successful
redirect; wrap the call to handleSendEmailToolOutputs(responseMessages) in its
own try/catch so email post-processing failures are contained: on error call
sendErrorNotification({ ...body, path: "/api/chat", error: serializeError(err)
}) and console.error the error but do not rethrow—ensure the function still
returns { redirectPath } (and that handleChatStream will emit the redirect) even
if the email step fails; reference handleSendEmailToolOutputs,
sendErrorNotification, serializeError, and redirectPath when making the change.
- Line 19: Create a new utility file exporting the async function
copyChatMessages with the exact signature export async function
copyChatMessages(params: { sourceChatId: string; targetChatId: string;
clearExisting?: boolean; }): Promise<{ success: boolean; error?: string }>, move
the message-copying logic from copyChatMessagesHandler (the API route) into this
function (including loading source/target chats, optional clearing of existing
messages, copying message content/metadata, and error handling), and then update
copyChatMessagesHandler to call and return the result of copyChatMessages so the
handler only delegates to the utility; ensure all referenced symbols used by
handleChatCompletion.ts (import { copyChatMessages } from
"@/lib/chats/copyChatMessages") match the new module export.

In `@lib/chat/handleChatStream.ts`:
- Around line 56-62: The code emits a "data-redirect" stream event from
handleChatStream (writer.write with type "data-redirect" and data.path) but
there is no consumer for it; either remove this emission or implement a
client-side handler that consumes it. To fix: if keeping the feature, add logic
in the client stream message processor (the function that parses/dispatches
server stream messages — e.g., the onStreamMessage/processStreamEvent handler
used by the chat UI) to detect messages with type "data-redirect", read
data.path and perform navigation (router.push/replace or window.location) and
respect transient semantics, plus add unit/integration tests; otherwise remove
the writer.write({type: "data-redirect", ...}) call from handleChatStream to
eliminate dead code. Reference the writer.write call in handleChatStream.ts and
the app's stream message dispatch handler when implementing the consumer.

---

Nitpick comments:
In `@lib/chat/handleChatCompletion.ts`:
- Around line 26-49: The getCreateArtistResult function mixes traversal and
parsing—split it into two focused helpers: one scanner (e.g.,
scanForCreateArtistPart(responseMessages: UIMessage[]): UIPart | null) that
iterates responseMessages and message.parts to locate the matching part where
getToolOrDynamicToolName(part) === "create_new_artist" and part.state ===
"output-available", and a parser (e.g., parseCreateArtistResult(part: UIPart):
CreateNewArtistResult | null) that handles the two cases: if part.type ===
"dynamic-tool" read part.output?.content?.[0]?.text and try JSON.parse (return
null on parse errors), otherwise return part.output as CreateNewArtistResult;
then update getCreateArtistResult to call the scanner and parser and preserve
existing behavior (skip malformed outputs and return null if none found).
- Around line 120-145: The artist-room cloning/copy/redirect block should be
extracted into a small helper to keep handleChatCompletion focused: create a new
async function named processArtistRoomCopyAndGetRedirect(roomId: string,
responseMessages: MessageType[]) that encapsulates the getCreateArtistResult ->
copyRoom -> copyChatMessages logic and returns the redirectPath string |
undefined, preserving the same console.error paths on failures; replace the
inlined block in handleChatCompletion with a single await call to this helper
and return its result (ensuring handleSendEmailToolOutputs(responseMessages) is
still awaited afterward); keep the new helper under ~50 lines and only export it
if other modules need it.

In `@lib/chat/handleChatStream.ts`:
- Around line 44-74: The onFinish callback is too large and should be extracted
into a focused named helper: create an async function (e.g., handleOnFinishEvent
or finalizeChatStream) that accepts the event plus required locals (writer,
body, streamResult, DEFAULT_MODEL) and move the entire callback body into
it—preserve the early return when event.isAborted, the assistantMessages
selection logic, the call to handleChatCompletion (with body and
responseMessages), emitting the redirect via writer.write if redirectPath
exists, emitting the finish event with finishReason, and calling
handleChatCredits using await streamResult!.usage and body.model ??
DEFAULT_MODEL; then replace the inline onFinish implementation with onFinish:
async event => await handleOnFinishEvent(event, writer, body, streamResult).
Ensure you keep the same async/await behavior and imports/closures so types and
behavior remain unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d2a00217-e043-47d2-a1f6-82d6dd3e151f

📥 Commits

Reviewing files that changed from the base of the PR and between 7e203f8 and cf9f181.

⛔ Files ignored due to path filters (3)
  • lib/chat/__tests__/handleChatCompletion.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/chat/__tests__/handleChatStream.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/mcp/tools/artists/__tests__/registerCreateNewArtistTool.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (3)
  • lib/chat/handleChatCompletion.ts
  • lib/chat/handleChatStream.ts
  • lib/mcp/tools/artists/registerCreateNewArtistTool.ts
✅ Files skipped from review due to trivial changes (1)
  • lib/mcp/tools/artists/registerCreateNewArtistTool.ts

import { serializeError } from "@/lib/errors/serializeError";
import type { ChatRequestBody } from "./validateChatRequest";
import { copyRoom } from "@/lib/rooms/copyRoom";
import { copyChatMessages } from "@/lib/chats/copyChatMessages";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether the helper exists at the imported path and whether Vitest resolves tsconfig aliases.
fd -i '^copyChatMessages\.(ts|tsx|js|jsx)$' lib
fd -i 'vitest.config.*' .
fd -i 'vite.config.*' .
rg -n -C2 'copyChatMessages' lib/chat/handleChatCompletion.ts
rg -n 'vite-tsconfig-paths|resolve\.alias|compilerOptions|paths' tsconfig*.json vite.config.* vitest.config.* 2>/dev/null || true

Repository: recoupable/api

Length of output: 603


🏁 Script executed:

#!/bin/bash
# Search for copyChatMessages definition in the entire codebase
rg -l 'copyChatMessages' lib --type ts --type tsx

# Check directory structure
echo "=== lib/chat directory ==="
ls -la lib/chat 2>/dev/null || echo "lib/chat not found"

echo "=== lib/chats directory ==="
ls -la lib/chats 2>/dev/null || echo "lib/chats not found"

# Search for where copyChatMessages is exported/defined
echo "=== Search for copyChatMessages definition ==="
rg 'export.*copyChatMessages|function copyChatMessages|const copyChatMessages' lib --type ts --type tsx

# Check the import path more carefully
echo "=== Check exact import and usage context ==="
rg -B2 -A2 'from.*copyChatMessages' lib --type ts --type tsx

Repository: recoupable/api

Length of output: 3513


🏁 Script executed:

#!/bin/bash
# Check what's exported from copyChatMessagesHandler.ts
echo "=== copyChatMessagesHandler.ts content ==="
cat -n lib/chats/copyChatMessagesHandler.ts

# Search for copyChatMessages anywhere in the codebase (using rg without type flags)
echo ""
echo "=== Search for copyChatMessages function name ==="
rg 'copyChatMessages' . --include='*.ts' --include='*.tsx' -B1 -A1

Repository: recoupable/api

Length of output: 4279


🏁 Script executed:

#!/bin/bash
# View the entire handleChatCompletion.ts file to see how copyChatMessages is used
cat -n lib/chat/handleChatCompletion.ts

Repository: recoupable/api

Length of output: 6347


Create the missing copyChatMessages utility function.

The import on line 19 references @/lib/chats/copyChatMessages, but this file doesn't exist. copyChatMessagesHandler.ts (the API route handler) is a different concern and cannot be used as a drop-in utility.

Create lib/chats/copyChatMessages.ts and export a function with the signature expected by the call on line 126:

export async function copyChatMessages(params: {
  sourceChatId: string;
  targetChatId: string;
  clearExisting?: boolean;
}): Promise<{ success: boolean; error?: string }>

Extract the message-copying logic from copyChatMessagesHandler into this utility, then have the handler delegate to it.

🧰 Tools
🪛 GitHub Actions: Unit Tests

[error] 19-19: Vitest failed to load module import. Cannot find module '@/lib/chats/copyChatMessages' imported from '/home/runner/work/api/api/lib/chat/handleChatCompletion.ts'. Vitest suggests using a paths resolver (e.g., vite-tsconfig-paths) or avoiding tsconfig aliases in Vitest config.

🪛 GitHub Check: test

[failure] 19-19: lib/chat/tests/integration/chatEndToEnd.test.ts
Error: Cannot find module '@/lib/chats/copyChatMessages' imported from '/home/runner/work/api/api/lib/chat/handleChatCompletion.ts'.

  • If you rely on tsconfig.json's "paths" to resolve modules, please install "vite-tsconfig-paths" plugin to handle module resolution.
  • Make sure you don't have relative aliases in your Vitest config. Use absolute paths instead. Read more: https://vitest.dev/guide/common-errors
    ❯ lib/chat/handleChatCompletion.ts:19:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/chat/handleChatCompletion.ts` at line 19, Create a new utility file
exporting the async function copyChatMessages with the exact signature export
async function copyChatMessages(params: { sourceChatId: string; targetChatId:
string; clearExisting?: boolean; }): Promise<{ success: boolean; error?: string
}>, move the message-copying logic from copyChatMessagesHandler (the API route)
into this function (including loading source/target chats, optional clearing of
existing messages, copying message content/metadata, and error handling), and
then update copyChatMessagesHandler to call and return the result of
copyChatMessages so the handler only delegates to the utility; ensure all
referenced symbols used by handleChatCompletion.ts (import { copyChatMessages }
from "@/lib/chats/copyChatMessages") match the new module export.

Comment on lines +142 to +153
// Process any email tool outputs
await handleSendEmailToolOutputs(responseMessages);

return { redirectPath };
} catch (error) {
sendErrorNotification({
...body,
path: "/api/chat",
error: serializeError(error),
});
console.error("Failed to save chat", error);
return {};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't let email post-processing erase a successful redirect.

If the room copy succeeds and handleSendEmailToolOutputs(responseMessages) throws, the outer catch returns {} and handleChatStream never emits redirectPath even though the new artist room already exists. Isolate the email side effect so unrelated failures do not strand the account in the old chat.

💡 Contain the email failure locally
-    // Process any email tool outputs
-    await handleSendEmailToolOutputs(responseMessages);
+    try {
+      await handleSendEmailToolOutputs(responseMessages);
+    } catch (emailError) {
+      console.error("Failed to process email tool outputs", emailError);
+      sendErrorNotification({
+        ...body,
+        path: "/api/chat",
+        error: serializeError(emailError),
+      });
+    }
 
     return { redirectPath };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/chat/handleChatCompletion.ts` around lines 142 - 153, The current flow
calls handleSendEmailToolOutputs(responseMessages) before returning
redirectPath, so any exception there causes the outer catch to return {} and
drop the successful redirect; wrap the call to
handleSendEmailToolOutputs(responseMessages) in its own try/catch so email
post-processing failures are contained: on error call sendErrorNotification({
...body, path: "/api/chat", error: serializeError(err) }) and console.error the
error but do not rethrow—ensure the function still returns { redirectPath } (and
that handleChatStream will emit the redirect) even if the email step fails;
reference handleSendEmailToolOutputs, sendErrorNotification, serializeError, and
redirectPath when making the change.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/chats/copyChatMessages.ts">

<violation number="1" location="lib/chats/copyChatMessages.ts:34">
P1: `clearExisting` delete-then-insert is non-atomic and can permanently erase target messages when insert fails.</violation>

<violation number="2" location="lib/chats/copyChatMessages.ts:40">
P2: Catch insert failures in `copyChatMessages` and return an error result instead of throwing.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

}

if (clearExisting) {
const deleted = await deleteMemories(targetChatId);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: clearExisting delete-then-insert is non-atomic and can permanently erase target messages when insert fails.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/chats/copyChatMessages.ts, line 34:

<comment>`clearExisting` delete-then-insert is non-atomic and can permanently erase target messages when insert fails.</comment>

<file context>
@@ -0,0 +1,54 @@
+  }
+
+  if (clearExisting) {
+    const deleted = await deleteMemories(targetChatId);
+    if (!deleted) {
+      return { status: "error", error: "Failed to clear target chat messages" };
</file context>
Fix with Cubic

}
}

const copiedCount = await insertMemories(
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Catch insert failures in copyChatMessages and return an error result instead of throwing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/chats/copyChatMessages.ts, line 40:

<comment>Catch insert failures in `copyChatMessages` and return an error result instead of throwing.</comment>

<file context>
@@ -0,0 +1,54 @@
+    }
+  }
+
+  const copiedCount = await insertMemories(
+    sourceMemories.map(memory => ({
+      id: generateUUID(),
</file context>
Fix with Cubic

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/chat/__tests__/handleChatCompletion.test.ts">

<violation number="1" location="lib/chat/__tests__/handleChatCompletion.test.ts:347">
P1: Custom agent: **Flag AI Slop and Fabricated Changes**

The redirect-flow test was changed to assert unrelated email handling, so the PR’s claimed redirect behavior is no longer exercised by this test. Restore an assertion on the redirect result to prevent normalizing regressions.</violation>
</file>

<file name="lib/chat/handleCreateArtistRedirect.ts">

<violation number="1" location="lib/chat/handleCreateArtistRedirect.ts:42">
P1: Validate that `body.roomId` belongs to the authenticated account before copying; otherwise this flow can copy another account’s chat history when given a foreign room ID.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

},
];

await handleChatCompletion(body, responseMessages);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Custom agent: Flag AI Slop and Fabricated Changes

The redirect-flow test was changed to assert unrelated email handling, so the PR’s claimed redirect behavior is no longer exercised by this test. Restore an assertion on the redirect result to prevent normalizing regressions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/chat/__tests__/handleChatCompletion.test.ts, line 347:

<comment>The redirect-flow test was changed to assert unrelated email handling, so the PR’s claimed redirect behavior is no longer exercised by this test. Restore an assertion on the redirect result to prevent normalizing regressions.</comment>

<file context>
@@ -362,15 +344,9 @@ describe("handleChatCompletion", () => {
       ];
 
-      const result = await handleChatCompletion(body, responseMessages);
+      await handleChatCompletion(body, responseMessages);
 
-      expect(mockCopyRoom).toHaveBeenCalledWith("room-456", "artist-123");
</file context>
Fix with Cubic

return undefined;
}

const newRoomId = await copyRoom(body.roomId ?? "", createArtistResult.artistAccountId);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Validate that body.roomId belongs to the authenticated account before copying; otherwise this flow can copy another account’s chat history when given a foreign room ID.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/chat/handleCreateArtistRedirect.ts, line 42:

<comment>Validate that `body.roomId` belongs to the authenticated account before copying; otherwise this flow can copy another account’s chat history when given a foreign room ID.</comment>

<file context>
@@ -0,0 +1,59 @@
+    return undefined;
+  }
+
+  const newRoomId = await copyRoom(body.roomId ?? "", createArtistResult.artistAccountId);
+  if (!newRoomId) {
+    console.error("Failed to create final artist conversation room");
</file context>
Fix with Cubic

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
lib/chat/handleChatStream.ts (1)

32-35: Extract the finish pipeline out of handleChatStream().

This function now owns request validation, agent setup, stream merging, message persistence, redirect hooks, manual finish emission, and credits. Pull the custom onFinish body into a dedicated helper so the control flow stays smaller and the error paths are easier to test.

As per coding guidelines, **/*.{js,ts,tsx,jsx,py,java,cs,go,rb,php}: Flag functions longer than 20 lines or classes with >200 lines; Keep functions small and focused

Also applies to: 54-80

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/chat/handleChatStream.ts` around lines 32 - 35, The finish pipeline
inside handleChatStream is too large and should be extracted into a dedicated
helper to simplify control flow and make error paths testable: create a new
function (e.g., finalizeChatStream or handleChatFinish) that encapsulates the
current onFinish body and all finish-related responsibilities (stream merging,
message persistence, redirect hooks, manual finish emission, credits update, and
any error handling), accept the minimal required inputs (request context,
agent/result objects, and onFinish callback) and return a normalized result or
throw; then replace the inline finish logic in handleChatStream with a single
call to this new helper, update tests to call the helper directly for
finish-path coverage, and ensure exported types/signatures (handleChatStream,
onFinish) remain compatible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/chat/handleChatStream.ts`:
- Around line 55-79: The callback passed to streamResult.toUIMessageStream (the
onFinish handler) currently performs side effects (calling handleChatCompletion,
onFinish hook, writer.write with type "finish", and handleChatCredits) without a
finally, so exceptions can skip emitting the manual finish chunk; refactor the
onFinish async handler to perform the side effects (await handleChatCompletion
and await onFinish) inside a try block and ensure the writer.write({type:
"finish", finishReason: ...}) and the handleChatCredits call run in a finally
block (still awaiting any necessary promises) so the finish chunk is always
emitted even if handleChatCompletion or onFinish throws.

In `@lib/chat/handleCreateArtistRedirect.ts`:
- Around line 42-55: The code creates a new room via copyRoom(...) and then
calls copyChatMessages(...); if copyChatMessages fails the function logs and
returns, leaving the created room behind—fix by deleting the newly created room
on any copy failure (e.g., call a teardown like deleteRoom(newRoomId) or
removeRoom(newRoomId) immediately before returning undefined) or refactor into a
single helper (e.g., createRoomWithHistory or copyRoomAndMessages) that creates
the room and copies history atomically so failures roll back; ensure you
reference copyRoom, newRoomId and copyChatMessages when adding the rollback or
the new atomic helper.

In `@lib/chats/copyChatMessages.ts`:
- Around line 33-53: The clear-and-copy flow can leave the target empty if
insertMemories fails or inserts fewer rows than sourceMemories; update this by
performing delete+insert atomically or validating row counts: create a new
Supabase helper in lib/supabase (e.g., a transactional function for the chat
messages table) that accepts targetChatId and an array of new rows (use
generateUUID() for ids and preserve memory.updated_at/content), run the delete
and bulk insert inside a single transaction, and return success only if the
inserted row count equals sourceMemories.length; alternatively, if you can't use
a DB transaction, at minimum check copiedCount against sourceMemories.length and
return an error when they differ and move all Supabase calls
(deleteMemories/insertMemories) into lib/supabase/[table]/[function].ts.

---

Nitpick comments:
In `@lib/chat/handleChatStream.ts`:
- Around line 32-35: The finish pipeline inside handleChatStream is too large
and should be extracted into a dedicated helper to simplify control flow and
make error paths testable: create a new function (e.g., finalizeChatStream or
handleChatFinish) that encapsulates the current onFinish body and all
finish-related responsibilities (stream merging, message persistence, redirect
hooks, manual finish emission, credits update, and any error handling), accept
the minimal required inputs (request context, agent/result objects, and onFinish
callback) and return a normalized result or throw; then replace the inline
finish logic in handleChatStream with a single call to this new helper, update
tests to call the helper directly for finish-path coverage, and ensure exported
types/signatures (handleChatStream, onFinish) remain compatible.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0acb0b5d-510e-4806-a10b-ba5a9b6866b6

📥 Commits

Reviewing files that changed from the base of the PR and between cf9f181 and e3d8ab6.

⛔ Files ignored due to path filters (4)
  • lib/chat/__tests__/handleChatCompletion.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/chat/__tests__/handleChatStream.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/chat/__tests__/handleCreateArtistRedirect.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/chats/__tests__/copyChatMessagesHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (5)
  • app/api/chat/route.ts
  • lib/chat/handleChatStream.ts
  • lib/chat/handleCreateArtistRedirect.ts
  • lib/chats/copyChatMessages.ts
  • lib/chats/copyChatMessagesHandler.ts

Comment on lines +55 to +79
streamResult.toUIMessageStream({
sendFinish: false,
onFinish: async event => {
if (event.isAborted) {
return;
}

const assistantMessages = event.messages.filter(
message => message.role === "assistant",
);
const responseMessages =
assistantMessages.length > 0 ? assistantMessages : [event.responseMessage];
await handleChatCompletion(body, responseMessages);
await onFinish?.({ body, responseMessages, writer });

writer.write({
type: "finish",
finishReason: event.finishReason,
});

await handleChatCredits({
usage: await streamResult!.usage,
model: body.model ?? DEFAULT_MODEL,
accountId: body.accountId,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Always emit the manual finish chunk from a finally.

With sendFinish: false, this callback is now responsible for closing the stream. Any rejection from handleChatCompletion() or the injected onFinish hook skips the finish write, which can leave the client waiting indefinitely. Put the side effects behind a try/finally so finish is sent even when the redirect flow fails.

Possible fix
               const responseMessages =
                 assistantMessages.length > 0 ? assistantMessages : [event.responseMessage];
-              await handleChatCompletion(body, responseMessages);
-              await onFinish?.({ body, responseMessages, writer });
-
-              writer.write({
-                type: "finish",
-                finishReason: event.finishReason,
-              });
+              try {
+                await handleChatCompletion(body, responseMessages);
+                await onFinish?.({ body, responseMessages, writer });
+              } catch (error) {
+                console.error("/api/chat onFinish error:", error);
+              } finally {
+                writer.write({
+                  type: "finish",
+                  finishReason: event.finishReason,
+                });
+              }
 
               await handleChatCredits({
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
streamResult.toUIMessageStream({
sendFinish: false,
onFinish: async event => {
if (event.isAborted) {
return;
}
const assistantMessages = event.messages.filter(
message => message.role === "assistant",
);
const responseMessages =
assistantMessages.length > 0 ? assistantMessages : [event.responseMessage];
await handleChatCompletion(body, responseMessages);
await onFinish?.({ body, responseMessages, writer });
writer.write({
type: "finish",
finishReason: event.finishReason,
});
await handleChatCredits({
usage: await streamResult!.usage,
model: body.model ?? DEFAULT_MODEL,
accountId: body.accountId,
});
streamResult.toUIMessageStream({
sendFinish: false,
onFinish: async event => {
if (event.isAborted) {
return;
}
const assistantMessages = event.messages.filter(
message => message.role === "assistant",
);
const responseMessages =
assistantMessages.length > 0 ? assistantMessages : [event.responseMessage];
try {
await handleChatCompletion(body, responseMessages);
await onFinish?.({ body, responseMessages, writer });
} catch (error) {
console.error("/api/chat onFinish error:", error);
} finally {
writer.write({
type: "finish",
finishReason: event.finishReason,
});
}
await handleChatCredits({
usage: await streamResult!.usage,
model: body.model ?? DEFAULT_MODEL,
accountId: body.accountId,
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/chat/handleChatStream.ts` around lines 55 - 79, The callback passed to
streamResult.toUIMessageStream (the onFinish handler) currently performs side
effects (calling handleChatCompletion, onFinish hook, writer.write with type
"finish", and handleChatCredits) without a finally, so exceptions can skip
emitting the manual finish chunk; refactor the onFinish async handler to perform
the side effects (await handleChatCompletion and await onFinish) inside a try
block and ensure the writer.write({type: "finish", finishReason: ...}) and the
handleChatCredits call run in a finally block (still awaiting any necessary
promises) so the finish chunk is always emitted even if handleChatCompletion or
onFinish throws.

Comment on lines +42 to +55
const newRoomId = await copyRoom(body.roomId ?? "", createArtistResult.artistAccountId);
if (!newRoomId) {
console.error("Failed to create final artist conversation room");
return undefined;
}

const copyResult = await copyChatMessages({
sourceChatId: body.roomId ?? "",
targetChatId: newRoomId,
clearExisting: true,
});
if (copyResult.status === "error") {
console.error(copyResult.error);
return undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Roll back the new room when the copy step fails.

copyRoom() has already created the destination room before copyChatMessages() runs. If the copy fails, this path only logs and returns undefined, leaving an empty artist chat behind. Please delete the new room on failure, or combine room creation and history copy into one helper that succeeds atomically.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/chat/handleCreateArtistRedirect.ts` around lines 42 - 55, The code
creates a new room via copyRoom(...) and then calls copyChatMessages(...); if
copyChatMessages fails the function logs and returns, leaving the created room
behind—fix by deleting the newly created room on any copy failure (e.g., call a
teardown like deleteRoom(newRoomId) or removeRoom(newRoomId) immediately before
returning undefined) or refactor into a single helper (e.g.,
createRoomWithHistory or copyRoomAndMessages) that creates the room and copies
history atomically so failures roll back; ensure you reference copyRoom,
newRoomId and copyChatMessages when adding the rollback or the new atomic
helper.

Comment on lines +33 to +53
if (clearExisting) {
const deleted = await deleteMemories(targetChatId);
if (!deleted) {
return { status: "error", error: "Failed to clear target chat messages" };
}
}

const copiedCount = await insertMemories(
sourceMemories.map(memory => ({
id: generateUUID(),
room_id: targetChatId,
content: memory.content,
updated_at: memory.updated_at,
})),
);

return {
status: "success",
copiedCount,
clearedExisting: clearExisting,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Make the clear-and-copy step atomic.

deleteMemories() runs before the insert, and insertMemories() only gives you a row count. If the insert fails after the delete, or inserts fewer rows than sourceMemories.length, this helper can empty the target chat and still return status: "success". Please move the delete+insert into a single transactional Supabase function, or at least fail when the inserted count does not match the source count.

As per coding guidelines, lib/!(supabase)/**/*.{ts,tsx}: all Supabase database calls must be in lib/supabase/[table_name]/[function].ts

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/chats/copyChatMessages.ts` around lines 33 - 53, The clear-and-copy flow
can leave the target empty if insertMemories fails or inserts fewer rows than
sourceMemories; update this by performing delete+insert atomically or validating
row counts: create a new Supabase helper in lib/supabase (e.g., a transactional
function for the chat messages table) that accepts targetChatId and an array of
new rows (use generateUUID() for ids and preserve memory.updated_at/content),
run the delete and bulk insert inside a single transaction, and return success
only if the inserted row count equals sourceMemories.length; alternatively, if
you can't use a DB transaction, at minimum check copiedCount against
sourceMemories.length and return an error when they differ and move all Supabase
calls (deleteMemories/insertMemories) into lib/supabase/[table]/[function].ts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant